#include "Url.hpp"

ZEND_BEGIN_ARG_INFO_EX(arginfo_registry_start, 0, 0, 2)
	ZEND_ARG_INFO(0, key)
	ZEND_ARG_INFO(0, title)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_registry_end, 0, 0, 1)
	ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_registry_setEnable, 0, 0, 1)
	ZEND_ARG_INFO(0, flag)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_registry_void, 0, 0, 1)
ZEND_END_ARG_INFO()

static zend_function_entry php_debug_profiler_methods[] = {
	PHP_ME(Profiler, start,     arginfo_registry_start,       ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
	PHP_ME(Profiler, end,       arginfo_registry_end,         ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
	PHP_ME(Profiler, setEnable, arginfo_registry_setEnable,   ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
	PHP_ME(Profiler, getAll,    arginfo_registry_void,        ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
	PHP_ME(Profiler, closeAll,  arginfo_registry_void,        ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
	PHP_FE_END
}; 

static inline void init_class_debug_profiler() {
	zend_class_entry ce; 
	
	// Evilworlds.Debug.Profiler
	INIT_CLASS_ENTRY(ce, DEBUG_PROFILER_CLASS_NAME, php_debug_profiler_methods); 
	php_debug_profiler_ce = zend_register_internal_class_ex(&ce, zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC); 
	memcpy(&php_debug_profiler_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); 
	php_debug_profiler_object_handlers.clone_obj = NULL; 
	php_debug_profiler_ce -> ce_flags |= ZEND_ACC_FINAL_CLASS; 
	
	zend_declare_property_null(php_debug_profiler_ce, const_str_length("points"), ZEND_ACC_STATIC | ZEND_ACC_PUBLIC TSRMLS_CC); 
	zend_declare_property_bool(php_debug_profiler_ce, const_str_length("enabled"), false, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC TSRMLS_CC); 
}

static inline void destroy_class_debug_profiler() {
	
}

PHP_METHOD(Profiler, start) {
	zval *pool, **checkpoint; 
	char *key; int key_len; 
	char *title; int title_len; 
	timeval start_time_val; 
	
	zval *is_enabled = zend_read_static_property(php_debug_profiler_ce, const_str_length("enabled"), false TSRMLS_CC); 
	if (!is_enabled || !Z_BVAL_P(is_enabled))
		return; 
	
	gettimeofday(&start_time_val, NULL); 
	
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key, &key_len, &title, &title_len) != SUCCESS)
		WRONG_PARAM_COUNT; 
	pool = zend_read_static_property(php_debug_profiler_ce, const_str_length("points"), false TSRMLS_CC); 
	if (Z_TYPE_P(pool) != IS_ARRAY)
		array_init(pool); 
	
	ulong h = zend_hash_func(key, key_len + 1); 
	if (zend_hash_quick_find(Z_ARRVAL_P(pool), key, key_len + 1, h, (void **)&checkpoint) == SUCCESS) {
		if (Z_TYPE_PP(checkpoint) == IS_ARRAY) {
			ulong h_count      = zend_hash_func(const_obj_size("count")); 
			ulong h_start_time = zend_hash_func(const_obj_size("start_time")); 
			
			zval **count, **start_time; 
			if (zend_hash_quick_find(Z_ARRVAL_PP(checkpoint), const_obj_size("count"), h_count, (void **)&count) == SUCCESS) {
				if (Z_TYPE_PP(count) != IS_LONG)
					convert_to_long_ex(count); 
				++Z_LVAL_PP(count); 
			}
			
			if (zend_hash_quick_find(Z_ARRVAL_PP(checkpoint), const_obj_size("start_time"), h_start_time, (void **)&start_time) == SUCCESS) {
				if (Z_TYPE_PP(start_time) != IS_LONG)
					convert_to_long_ex(start_time); 
				Z_LVAL_PP(start_time) = ((unsigned long long)start_time_val.tv_sec * 1000000) + start_time_val.tv_usec; 
			}
			Z_ADDREF_PP(checkpoint); 
		} else
			php_error(E_WARNING, "Profiler::start(): checkpoint is not array! (type = %d)", Z_TYPE_PP(checkpoint));  
	} else {
		zval *point; 
		MAKE_STD_ZVAL(point); 
		array_init(point); 
		
		add_assoc_stringl_ex(point, const_obj_size("title"), title, title_len, 1); 
		add_assoc_long_ex(point, const_obj_size("count"), 1); 
		add_assoc_long_ex(point, const_obj_size("start_time"), ((unsigned long long)start_time_val.tv_sec * 1000000) + start_time_val.tv_usec); 
		add_assoc_long_ex(point, const_obj_size("total_time"), 0); 
		Z_ADDREF_P(point); 
		
		zend_hash_quick_add(Z_ARRVAL_P(pool), key, key_len + 1, h, &point, sizeof(zval *), NULL); 
	}
}

PHP_METHOD(Profiler, end) {
	zval *pool, **checkpoint = NULL; 
	char *key; int key_len; 
	timeval start_time_val; 
	
	zval *is_enabled = zend_read_static_property(php_debug_profiler_ce, const_str_length("enabled"), false TSRMLS_CC); 
	if (!is_enabled || !Z_BVAL_P(is_enabled))
		return; 
	
	gettimeofday(&start_time_val, NULL); 
	
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) != SUCCESS)
		WRONG_PARAM_COUNT; 
	pool = zend_read_static_property(php_debug_profiler_ce, const_str_length("points"), false TSRMLS_CC); 
	if (Z_TYPE_P(pool) != IS_ARRAY)
		return; 
	
	ulong h = zend_hash_func(key, key_len + 1); 
	if (zend_hash_quick_find(Z_ARRVAL_P(pool), key, key_len + 1, h, (void **)&checkpoint) == SUCCESS) {
		if (Z_TYPE_PP(checkpoint) == IS_ARRAY) {
			ulong h_total_time = zend_hash_func(const_obj_size("total_time")); 
			ulong h_start_time = zend_hash_func(const_obj_size("start_time")); 
			zval **total_time, **start_time; 
			
			if (zend_hash_quick_find(Z_ARRVAL_PP(checkpoint), const_obj_size("start_time"), h_start_time, (void **)&start_time) == SUCCESS) {
				if (Z_TYPE_PP(start_time) != IS_LONG)
					convert_to_long_ex(start_time); 
				if (Z_LVAL_PP(start_time) > 0) {
					if (zend_hash_quick_find(Z_ARRVAL_PP(checkpoint), const_obj_size("total_time"), h_total_time, (void **)&total_time) == SUCCESS) {
						if (Z_TYPE_PP(total_time) != IS_LONG)
							convert_to_long_ex(total_time); 
						Z_LVAL_PP(total_time) = (((unsigned long long)start_time_val.tv_sec * 1000000) + start_time_val.tv_usec) - Z_LVAL_PP(start_time); 
					}
					Z_LVAL_PP(start_time) = 0; 
				} else
					php_error(E_WARNING, "Profiler::end(): point %s already closed!", key); 
			}
		} else
			php_error(E_WARNING, "Profiler::end(): checkpoint is not array! (type = %d)", Z_TYPE_PP(checkpoint)); 
	} else
		php_error(E_WARNING, "Profiler::end(): checkpoint %s not found!", key); 
}

PHP_METHOD(Profiler, setEnable) {
	zend_bool enable; 
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &enable) != SUCCESS)
		WRONG_PARAM_COUNT; 
	zend_update_static_property_bool(php_debug_profiler_ce, const_str_length("enabled"), enable TSRMLS_CC); 
}

PHP_METHOD(Profiler, getAll) {
	zval *pool = zend_read_static_property(php_debug_profiler_ce, const_str_length("points"), false TSRMLS_CC); 
	if (!pool || Z_TYPE_P(pool) != IS_ARRAY) {
		array_init(return_value); 
		return; 
	}
	RETURN_ZVAL(pool, true, false); 
}

PHP_METHOD(Profiler, closeAll) {
	int key_type; 
	HashPosition pos; 
	char   *string_key; 
	uint    string_key_len; 
	timeval start_time_val; 
	ulong   num_key, h_total_time, h_start_time; 
	zval  **checkpoint, *pool, **total_time, **start_time; 
	
	gettimeofday(&start_time_val, NULL); 
	
	pool = zend_read_static_property(php_debug_profiler_ce, const_str_length("points"), false TSRMLS_CC); 
	if (Z_TYPE_P(pool) != IS_ARRAY)
		return; 
	
	zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(pool), &pos); 
	while (zend_hash_get_current_data_ex(Z_ARRVAL_P(pool), (void **)&checkpoint, &pos) == SUCCESS) {
		if ((key_type = zend_hash_get_current_key_ex(Z_ARRVAL_P(pool), &string_key, &string_key_len, &num_key, 1, &pos)) != HASH_KEY_IS_STRING) {
			php_error(E_WARNING, "Profiler::closeAll(): Invalid checkpoint key type %d!", key_type); 
			continue; 
		}
		
		if (Z_TYPE_PP(checkpoint) == IS_ARRAY) {
			h_total_time = zend_hash_func(const_obj_size("total_time")); 
			h_start_time = zend_hash_func(const_obj_size("start_time")); 
			
			if (zend_hash_quick_find(Z_ARRVAL_PP(checkpoint), const_obj_size("start_time"), h_start_time, (void **)&start_time) == SUCCESS) {
				if (Z_TYPE_PP(start_time) != IS_LONG)
					convert_to_long_ex(start_time); 
				if (Z_LVAL_PP(start_time) > 0) {
					php_error(E_WARNING, "Profiler::closeAll(): point %s not closed!", string_key); 
					if (zend_hash_quick_find(Z_ARRVAL_PP(checkpoint), const_obj_size("total_time"), h_total_time, (void **)&total_time) == SUCCESS) {
						if (Z_TYPE_PP(total_time) != IS_LONG)
							convert_to_long_ex(total_time); 
						Z_LVAL_PP(total_time) = (((unsigned long long)start_time_val.tv_sec * 1000000) + start_time_val.tv_usec) - Z_LVAL_PP(start_time); 
					}
					Z_LVAL_PP(start_time) = 0; 
				}
			}
		} else
			php_error(E_WARNING, "Profiler::closeAll(): checkpoint is not array! (type = %d)", Z_TYPE_PP(checkpoint)); 
		zend_hash_move_forward_ex(Z_ARRVAL_P(pool), &pos); 
	}
}
